perf(core): acquire snapshots concurrently - #2011
Conversation
|
Greptile (@greptileai) review |
|
Claude (@claude) review |
|
Claude finished Nikhil (@shadowfax92)'s task in 4m 22s —— View job Review: concurrent snapshot acquisitionWent through What's good
Minor findings (non-blocking)
Not verified
Nothing here blocks merging; both findings are low-severity and non-functional. |
|
DeepScan’s UNUSED_EXPR finding was a false positive for the cursor asset: CDP Runtime.callFunctionOn consumes the file as an uninvoked functionDeclaration. Added the documented rule-specific suppression plus a purpose comment in e226a2a; the 16 acquisition tests, formatting, diff check, and Biome hook are green. |
Greptile SummarySnapshot acquisition now overlaps accessibility, cursor, document-validation, and sibling-frame CDP work while preserving deterministic assembly.
Confidence Score: 2/5The reconnect-sensitive frame-session cache and stale child-frame commit path must be corrected before this PR is safe to merge. Prior-epoch sessions remain selectable after reconnect, and child-frame loader changes discard only ref identity while retaining AX data and cursor hits acquired from the replaced document. packages/browseros-agent/crates/browseros-core/src/frames.rs, packages/browseros-agent/crates/browseros-core/src/observer/acquisition.rs Important Files Changed
Sequence DiagramsequenceDiagram
participant Observer
participant Budget as SnapshotBudget
participant AX as Accessibility
participant Runtime
participant Frames as Child Frames
Observer->>Budget: Begin capture
par Acquire AX tree
Budget->>AX: getFullAXTree
AX-->>Observer: nodes
and Scan cursor candidates
Budget->>Runtime: evaluate/callFunctionOn
Runtime-->>Observer: candidate handles
and Validate document
Budget->>Frames: getFrameTree
Frames-->>Observer: loader identities
end
par Acquire sibling frames
Observer->>Frames: acquire child A
and
Observer->>Frames: acquire child B
end
Observer->>Observer: Restore stitch order
Observer->>Observer: Sequentially render and assign refs
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
packages/browseros-agent/crates/browseros-core/src/frames.rs:97-102
**Stale sessions survive reconnects**
When CDP reconnects after a same-process frame session has been cached, this lookup returns the prior connection epoch's session because only `PageManager` clears its sessions. Resolving an existing frame ref then sends click, typing, scrolling, or annotation commands through an invalid session and fails with an unknown or detached-session protocol error.
### Issue 2 of 2
packages/browseros-agent/crates/browseros-core/src/observer/acquisition.rs:294-296
**Changed child snapshots remain committable**
When a child frame navigates after its AX data is acquired, revalidation reports the changed loader but only clears `document_id`; the old nodes and cursor hits are still returned and committed because capture-level stability checks cover only the main frame. The resulting snapshot exposes stale child content and actionable refs from the replaced document.
Reviews (1): Last reviewed commit: "fix: suppress intentional cursor asset e..." | Re-trigger Greptile |
Greptile SummaryConcurrent snapshot acquisition is introduced while preserving sequential rendering semantics.
Confidence Score: 5/5The pull request appears safe to merge, with concurrency bounded locally and historical snapshot assembly behavior preserved. The changed acquisition paths restore deterministic stitch order before sequential ref mutation, isolate cursor markers per capture, clean remote objects on success and cancellation, and retain snapshot retry and child-failure rollback behavior. Important Files Changed
Sequence DiagramsequenceDiagram
participant Observer
participant Budget as SnapshotBudget
participant Frames as FrameRegistry
participant CDP
participant Renderer
Observer->>Budget: Read initial frame tree
par Acquire AX tree
Budget->>CDP: Accessibility.getFullAXTree
and Acquire cursor candidates
Budget->>CDP: Runtime.evaluate/callFunctionOn
Budget->>CDP: Runtime.getProperties
Budget->>CDP: DOM.describeNode (bounded)
and Validate document
Budget->>CDP: Page.getFrameTree
end
Observer->>Renderer: Render immutable frame inputs
Renderer-->>Observer: Iframe stitches
par Acquire sibling frames
Observer->>Frames: Resolve inherited target sessions
Frames->>CDP: Acquire child frame inputs
end
Observer->>Renderer: Assemble children in reverse stitch order
Observer->>Budget: Read final frame tree
Observer-->>Observer: Commit snapshot or retry
Reviews (2): Last reviewed commit: "fix: suppress intentional cursor asset e..." | Re-trigger Greptile |
|
Addressed both non-blocking Claude findings in 71871c9: expected visited-frame cycle skips no longer label sibling acquisition as partial, and cursor acquisition now deserializes Runtime.getProperties through the generated protocol result type. Formatting, workspace Clippy with warnings denied, and the full workspace test suite are green. |
✅ Tests passed — 1694/1698
|
* chore(deps): bump golang.org/x/crypto to v0.52.0 in apps/cli (#1994) Refresh go.sum and the x/sys transitive dependency. * chore(deps): bump the uv group across 1 directory with 5 updates (#1995) Bumps the uv group with 5 updates in the /packages/browseros directory: | Package | From | To | | --- | --- | --- | | [requests](https://github.com/psf/requests) | `2.32.5` | `2.33.0` | | [python-dotenv](https://github.com/theskumar/python-dotenv) | `1.2.1` | `1.2.2` | | [cryptography](https://github.com/pyca/cryptography) | `46.0.3` | `48.0.1` | | [idna](https://github.com/kjd/idna) | `3.11` | `3.15` | | [urllib3](https://github.com/urllib3/urllib3) | `2.5.0` | `2.7.0` | Updates `requests` from 2.32.5 to 2.33.0 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](psf/requests@v2.32.5...v2.33.0) Updates `python-dotenv` from 1.2.1 to 1.2.2 - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md) - [Commits](theskumar/python-dotenv@v1.2.1...v1.2.2) Updates `cryptography` from 46.0.3 to 48.0.1 - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](pyca/cryptography@46.0.3...48.0.1) Updates `idna` from 3.11 to 3.15 - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md) - [Commits](kjd/idna@v3.11...v3.15) Updates `urllib3` from 2.5.0 to 2.7.0 - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](urllib3/urllib3@2.5.0...2.7.0) --- updated-dependencies: - dependency-name: requests dependency-version: 2.33.0 dependency-type: direct:production dependency-group: uv - dependency-name: python-dotenv dependency-version: 1.2.2 dependency-type: direct:production dependency-group: uv - dependency-name: cryptography dependency-version: 48.0.1 dependency-type: direct:production dependency-group: uv - dependency-name: idna dependency-version: '3.15' dependency-type: indirect dependency-group: uv - dependency-name: urllib3 dependency-version: 2.7.0 dependency-type: indirect dependency-group: uv ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): pin transitive dependency resolutions in browseros-agent (#1997) Refresh a batch of pinned transitive resolutions to their current patch releases via overrides and update bun.lock. * Revert "fix(claw-app): scale Audit tab navigation (#1987)" (#1998) This reverts commit ee46ec4. * feat(claw): rewrite BrowserClaw harness skill (#1999) Frontmatter description now carries the standing instruction — default to BrowserClaw for browser work unprompted, over in-app browser surfaces — and the body becomes a compact operating manual: session etiquette, task-owned tabs, the snapshot -> act -> verify loop, read/grep and tool escalation, failure handling, and the untrusted-page-content rule. Validation test probes and line budget updated to match. * fix(agent): reconcile aliased skill targets by identity (#2001) * fix(agent): reconcile aliased skill targets by identity * fix(claw): use app resources in local launchers * test(claw): assert physical skill manifest paths * fix: address review findings for browserclaw-skill-target-identity * feat(claw): show total tokens consumed on Audit surfaces (#2000) * feat(claw): show total tokens consumed on Audit surfaces Materialize per-session token estimates (input+output, summed across dispatches) onto the tasks table via recompute_task, so the total is available live and not only from the post-teardown efficiency projection. Expose it through the canonical OpenAPI path as an optional SessionSummary tokenUsage field, present only when the session has dispatches and every one carries token-estimator v1 (legacy/unmeasured sessions omit it). Both Audit surfaces render it from the one shared field: a compact Tokens column on the session list (exact count on hover, em dash when unmeasured) and a Tokens entry on the session-detail summary card. * fix(claw): sort unmeasured token totals last in both directions The Tokens-column accessor mapped unmeasured sessions to 0, which only sank them on a descending sort — an ascending sort surfaced them ahead of every measured session. Return undefined and use tanstack's sortUndefined:'last' so unmeasured rows stay at the bottom either way. * fix(claw): hide tokens in audit list (#2002) * fix(claw): ground screenshot token baseline (#2003) * fix(claw): ground screenshot token baseline * test(claw): update cockpit efficiency expectation * test(claw): preserve signed savings coverage * feat(claw): move audit observation off the tool path (#2005) * refactor(claw): separate tool observers from effects * feat(claw): queue audit observations in bounded worker * fix(claw): flush coalesced audit previews on session close * test(claw): verify async audit shutdown lifecycle * fix: address review findings for 260723-1154_async_audit_observer * fix(claw): sum tool durations for human time saved (#2004) * fix(claw): sum tool durations for time saved * fix(claw): preserve retained efficiency history * fix(claw): guard efficiency backfill from session reuse * fix(claw): stop cockpit saved-stats labels from overlapping (#2006) Both the "used" and "screenshot-first" token labels were absolutely positioned inside the same overflow-hidden budget track. Around the mid-range their text occupied the same pixels and painted through each other (e.g. "used 63.2K" over "112.3K"). Lift both numbers into a legend row above the bar — a flex justify-between row that wraps to two lines before the labels could ever meet — and keep the track as a pure gauge: accent fill = what BrowserClaw used, striped remainder = what a screenshot-first agent would have spent. The bar carries no text now, so nothing can collide or clip at any used/total ratio or label length. Accessibility, reduced-motion (the pulsing dot keeps motion-reduce), theme tokens, and the stats/API contract are unchanged. Tests assert the structural invariant (no labels inside the bounded bar) across low, mid, and full ratios, and the fill width replaces the old marker-position assertions. * refactor(patches): retire hidden window implementation (#2007) * refactor(patches): retire hidden window implementation * fix(patches): preserve single-close window teardown * chore: sync internal-docs submodule (#2008) Co-authored-by: browseros-bot <bot@browseros.ai> * chore: sync internal-docs submodule (#2009) Co-authored-by: browseros-bot <bot@browseros.ai> * fix(claw): use session duration for human time saved (#2010) * fix(claw): use session duration for saved time * fix(claw): backfill saved time from task durations * perf(core): acquire snapshots concurrently (#2011) * perf(core): batch cursor backend-node resolution * refactor(core): separate snapshot frame acquisition * perf(core): acquire snapshot sibling frames concurrently * perf(core): trace concurrent snapshot capture * fix: address review findings for snapshot-concurrent-acquisition * fix: address review findings for snapshot-concurrent-acquisition * fix: address review findings for snapshot-concurrent-acquisition * fix: suppress intentional cursor asset expression * fix(core): clear frame sessions after reconnect * fix(core): address snapshot PR review notes * feat(claw-app): default replays to 2x (#2015) * chore: refresh TypeScript CDP and retire hidden surfaces (#2014) * chore(cdp): refresh TypeScript protocol definitions * refactor(server): use background pages for scheduled tasks * refactor(browser-core): retire hidden page and window behavior * refactor(browser-mcp): remove hidden tab and window controls * test: repair TypeScript verification baselines * test(claw-app): prepare WXT aliases before tests * fix(claw): exclude red from automatic tab-group colors (#2016) * fix(claw): exclude red from automatic tab groups * test(claw): update automatic tab color fixture * test(claw): cover snapshot concurrency contracts (#2017) * test(claw): add snapshot concurrency fixtures * test(claw): cover live cursor snapshot acquisition * test(claw): cover concurrent iframe snapshot contracts * test(claw): stress overlapping snapshot contracts * fix: address review findings for snapshot-concurrency-contract-tests * fix: address review findings for snapshot-concurrency-contract-tests * refactor: refresh Rust CDP and retire hidden windows (#2013) * chore(cdp): sync Rust protocol from Chromium * refactor(core): retire hidden window state * refactor(mcp): remove hidden window controls * refactor(server): drop hidden window runtime references * test(core): align getProperties fixtures with protocol * test(mcp): retire hidden conformance cases * chore(release): build v0.48.3 [skip ci] Automated nightly macOS version bump. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nikhil <nikhilsv92@gmail.com> Co-authored-by: browseros-bot <bot@browseros.ai> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(code-mode): collapse to one audited, ownership-aware script tool (#1996) * feat(claw-server-rust): add parent_dispatch_id to tool_dispatches Adds the audit-hierarchy column that links a primitive dispatch to the script dispatch it ran inside. A nullable parent_dispatch_id lands on the tool_dispatches entity and RecordToolDispatchInput, is written by the audit insert, and ships as a named migration (m0008_add_parent_dispatch_id) following the existing nullable-column pattern. Schema and migration-count tests are updated for the new column and eighth migration. No behavior change yet: every current dispatch records parent_dispatch_id = None. The script inner-call hook that populates it for run/execute primitives lands in a follow-up commit. * feat(browseros-mcp): add InnerCallHook seam to ToolCtx Defines the InnerCallHook trait plus InnerCallRecord in the framework: a host-injected hook a script tool calls around each browser primitive to enforce per-primitive ownership (authorize) and record the primitive as a child audit row (record). browseros-mcp cannot reach the host's guards or audit store, so the host implements this and passes it in through the new optional ToolCtx.inner_call_hook field. No behavior change: the field defaults to None at every construction site, so nothing invokes the hook yet. The run bridge starts calling it in a follow-up commit. * feat(browseros-mcp): run bridge invokes the inner-call hook per primitive BrowserBridge now calls the injected InnerCallHook around every browser primitive: authorize before dispatch (a rejection throws into the script and skips execution) and record after. The bridge computes the target page from the method and arguments so the host can check ownership of the exact page a primitive touches. The match body moves into a dispatch() helper wrapped by call(). Threaded through execute_run -> execute_quickjs -> install_globals from ctx.inner_call_hook, so it stays None (a no-op) unless the host injects it. Covered by a mock-hook test asserting authorize/record fire with the right page, and that a rejection blocks the primitive before dispatch and records nothing. * feat(claw-server-rust): enforce ownership and audit script inner calls Implements the host InnerCallHook (ScriptInnerCallHook) and injects it for script tools at the dispatch site. Each browser primitive a run script executes is now: - ownership-checked: a page owned by another conversation is rejected with the same message the page_ownership guard uses, closing the isolation gap where a script could drive another agent's tab. Pages a script creates itself are unclaimed and stay usable; rejecting unclaimed pages at full guard parity needs claiming script-created pages and is a follow-up. - audited: recorded as a child tool_dispatches row carrying parent_dispatch_id = the script's dispatch id, so the cockpit can attribute and replay the primitives inside a run instead of seeing a black box. The hook is injected only for ARBITRARY_SCRIPT_TOOLS with a resolved identity; every other dispatch is unchanged. Covered by tests for the foreign-page rejection and the linked child-row write. * fix(claw-server-rust): satisfy clippy and integration-test constructors Recovers poisoned test locks via PoisonError::into_inner and replaces expect/unwrap in the new hook tests with ? and ok_or_else to satisfy the workspace clippy denial. Adds the parent_dispatch_id field to the two integration-test RecordToolDispatchInput constructors that the lib build did not cover. * feat(browseros-mcp): bring the run SDK to tool parity via handler routing Extends the browser SDK with the capabilities the session-direct bridge did not cover: read, grep, wait, screenshot, evaluate, download, pdf, upload, tabGroups, windows. Rather than reimplement each, a `tool:` bridge method dispatches through the real tool handler with execute_tool, so the script reaches full parity with the granular tool set and each primitive reuses that tool's argument validation and output. The SDK method supplies the page argument; opts mirror the same-named tool's arguments. The bridge now holds the full ToolCtx (session-direct primitives use ctx.session, tool-backed ones pass ctx to execute_tool, the inner-call hook lives at ctx.inner_call_hook), so the ownership + audit hook wraps tool-backed primitives too. Covered by a test routing browser.windows through the handler and asserting the hook authorizes and records it. * feat(claw-server-rust): collapse the MCP surface to the code-mode tool The host now exposes only the script tool plus name_session; the granular browser tools are filtered out of the catalog it serves. Because listing, lookup, and call all read the same catalog, filtering it both hides and blocks the granular tools. Their implementations stay in the crate as the functions the script bridge dispatches to, so no capability is lost: every granular tool is reachable inside the browser SDK. This removes the cheap per-call path whose existence stops agents from composing and persisting flows. The operating guide is rewritten for code mode: one tool that runs JavaScript against the browser SDK, the SDK surface, and the compose-the-whole-flow-in-one-script loop. Covered by a test asserting the granular tools are neither listed nor callable while the script tool and name_session remain. * feat(claw-server-rust): persistence seam for code-mode helpers Adds the storage layer the self-healing work builds on: reusable .js a script saves for a host and a later script loads by name, laid out as <browserclaw_dir>/helpers/<host>/<name>.js. Provides traversal-safe list/read/write over that convention, rejecting host or name segments that could escape the helpers root. The saveHelper primitive and the hot-load into the script runtime land with the self-healing work; this is only the seam so that work is additive. * feat(claw-api): expose script dispatch hierarchy on the audit contract The cockpit dispatch DTO now carries dispatchKey (this row's stable ULID) and parentDispatchId (the script dispatch a primitive ran inside), so the cockpit can group a script's primitives under it and replay them. Adds the two optional fields to the OpenAPI Dispatch schema and the generated model, and populates them from the audit row in the session detail mapper. Backend contract only; the cockpit frontend rendering of the group is a follow-on in the app. * refactor(claw-server-rust): hide granular tools from the list, keep them callable Collapses the surface by filtering listed_tools rather than the whole catalog, so the agent sees only the script tool plus name_session while the granular tools stay callable by name. This keeps the server's own dispatch and the end-to-end tests, which drive tools directly, working, while still removing the granular tools from what the agent can discover and reach for. Updates the initialize integration test to expect the collapsed list and the new code-mode instructions opener. * fix(claw-server-rust): claim and group pages a code-mode script creates A script's browser.pages.newPage called the session directly, so it skipped the ownership-claims and tab-groups effects that a `tabs new` runs: no page ownership claim, no session-tab window, no tab activity, and no per-agent tab group. Agents in code mode could step on each other and on the user's tabs, and the cockpit could not attribute the tab. Widens InnerCallHook with on_page_created, which the run bridge fires after a newPage returns. The host ScriptInnerCallHook now holds the script's ToolCall and, on page creation, reuses the same effect logic: record_new_page (extracted from ownership-claims) for the claim, tab activity, and session-tab window, and run_tab_group_work (from tab-groups) to ensure the agent's tab group and place the page in it. This is the keystone fix: the session-tab window it writes is also what replay attribution and per-tab screenshot selection depend on. * test(claw-server-rust): verify code-mode tabs open the replay window Session replays were missing because replay attribution joins a session's recordings through its session_tabs ownership window, which code-mode tabs never opened. The keystone fix (claiming pages a script creates) now opens that window; this locks it with a test that on_page_created writes the session_tab claim (tab id, target, claimed window) a code-mode tab needs for its recording to resolve into a replay. No production change beyond the keystone fix. * fix(claw-server-rust): capture per-step screenshots for code-mode primitives Screenshots were missing because the per-dispatch capture runs in the audit effect, which the script bridge bypasses; only the outer run got one, and even that failed with no owned tab to shoot. The script hook now captures a screenshot per page-targeting primitive, keyed to the child audit row, reusing the audit effect's persist_screenshot (made shareable, taking the row id). Page-less primitives (windows, tab groups) are skipped. Combined with the keystone claim, capture now has an owned tab to select. * fix(claw-server-rust): report code-mode session liveness and tab activity A code-mode session showed Idle with zero tabs and lingered in the cockpit "running" list, because the session touch, tab activity, and session-tab signals are populated only by the bypassed effects. The script hook now touches the session per primitive to keep the idle clock fresh and records tab activity for the touched page, so the cockpit sees the tab and the session progressing. Recorded before the per-step screenshot so capture selects the touched tab. The hard session-end path (transport close, idle sweep) is unchanged. * fix(browseros-mcp): give the run SDK an exact contract to stop tab churn Agents opened duplicate tabs and never closed them because the tool description did not state what each SDK call returns, so the agent probed at runtime (typeof, Object.keys, getOwnPropertyNames) and re-opened pages to inspect results. The description now states exact return shapes (newPage returns a numeric pageId, read returns a markdown string, and so on), tells the agent not to probe them, to reuse a pageId across steps, and to close a page with browser.pages.close when done. Also documents Promise.all for running independent primitives concurrently. * fix(claw-server-rust): make code-mode audits read as run-then-primitives The audit showed a script's primitives before its run row and labelled them with the internal tool: routing prefix, so it looked like tools ran without a run. Two fixes: the outer script dispatch is now stamped with its start time (created_at), so run sorts before the child primitives it records while executing; and the tool: prefix is stripped from child rows so they read as the plain capability (read, wait, screenshot). The full nesting of children under their run, via the parent_dispatch_id already on the contract, is cockpit-frontend work. * fix(browseros-mcp): read/grep return text, and correct the SDK contract The previous description claimed read returns a markdown string, but the bridge returned the tool's structured metadata ({ format, path, contentLength }); the agent got an object, saw md.length undefined, assumed the read failed, and re-opened tabs to debug, which is where the duplicate tabs came from. run_tool now returns the text for read and grep, so read is a string as documented. The description is also corrected where it was wrong: read and grep return text, and wait takes { for, value, timeout } (a plain pause is { value: ms }), not { ms }. * fix(claw-server-rust): order session dispatches by created_at, not write id The session-detail dispatches were ordered by row id, i.e. write order, so a script's child primitives (written while it executes) sorted before the script row (written when it finishes). The earlier created_at fix stamped the script with its start time but this query ignored it. Ordering by created_at (id breaks ties) puts a run before the primitives it contains, so the audit reads as run-then-its-steps. * fix(claw-server-rust): make the BrowserClaw harness skill a plain pointer The installed skill still told harnesses to follow a snapshot -> act -> verify tool loop, which describes the retired granular tools and steers agents to methods that no longer exist. The skill's only job is to say: if you need a browser, use BrowserClaw and prefer it over other browser surfaces. It now does exactly that and defers all how-to to the MCP initialize instructions and the run tool description, which are the single source of truth for the code-mode SDK. Test updated for the pointer shape. * fix(browseros-mcp): code-mode newPage opens in the background by default An agent opening a page stole the user's focus: the code-mode bridge left background unset, and Chrome opens an unspecified tab in the foreground, whereas the granular tabs-new tool defaults background=true ("open without stealing focus"). Code-mode newPage now defaults to a background tab, so a working agent does not interrupt the user; an explicit background:false opens it active, which the agent should do only when the user asks to bring a tab to the front. Documented in the SDK description. * perf(browseros-mcp): steer run scripts to batch and parallelize Audit review showed agents making one run call per item and processing serially, so a job spent nearly half its wall-clock in fixed per-page waits (14s of 30s on the price task). The description now instructs doing the whole task in as few run calls as possible, looping over all items in one call, and parallelizing independent work with Promise.all so N pages cost one wait cycle instead of N, with a worked example. This is guidance, not a hard limit; the concurrency was already supported. * feat(claw-server-rust): advertise multi-agent parallelism in the operating guide BrowserClaw's isolation model already gives every agent, including a subagent, its own tabs and tab group, so parallel agents cannot collide, but the guide did not say so, leaving that ceiling unused. The instructions now state the isolation guarantee and frame parallelism at two levels: batch primitives with Promise.all inside one run, and split a large workload across subagents (when the harness supports them), since each gets isolated tabs here. The per-agent tab budget stays at about 5. This lives in the operating guide, not the pointer skill. * fix(claw-server): tri-bucket ownership view for code-mode pages.list browser.pages.list() returned a raw, unclassified array of every open tab, so a code-mode script had no signal that a tab belonged to the user or another agent. A benign close-all cleanup over that list could close the user's tabs and empty the window. Return the same tri-bucket ownership view the granular tabs-list already produces: each page is tagged ownership "mine" | "user" | "other-agent" with ownerAgentId and ownerLabel. The engine crate cannot compute ownership, so annotation goes through a new InnerCallHook.annotate_pages seam the host implements, reusing the tabs-list-view classifier. This is classification, not enforcement: page authorization is unchanged (peer tabs rejected, the user's tabs allowed), so an agent still works on a user tab when asked. The run description and MCP instructions tell the agent to act on and close only its own tabs. * style: apply rustfmt to the code-mode crates * test(claw-mcp): expect the collapsed code-mode tool catalog * chore: sync feat/browserclaw-code-mode with main (#2021) * chore(deps): bump golang.org/x/crypto to v0.52.0 in apps/cli (#1994) Refresh go.sum and the x/sys transitive dependency. * chore(deps): bump the uv group across 1 directory with 5 updates (#1995) Bumps the uv group with 5 updates in the /packages/browseros directory: | Package | From | To | | --- | --- | --- | | [requests](https://github.com/psf/requests) | `2.32.5` | `2.33.0` | | [python-dotenv](https://github.com/theskumar/python-dotenv) | `1.2.1` | `1.2.2` | | [cryptography](https://github.com/pyca/cryptography) | `46.0.3` | `48.0.1` | | [idna](https://github.com/kjd/idna) | `3.11` | `3.15` | | [urllib3](https://github.com/urllib3/urllib3) | `2.5.0` | `2.7.0` | Updates `requests` from 2.32.5 to 2.33.0 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](psf/requests@v2.32.5...v2.33.0) Updates `python-dotenv` from 1.2.1 to 1.2.2 - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md) - [Commits](theskumar/python-dotenv@v1.2.1...v1.2.2) Updates `cryptography` from 46.0.3 to 48.0.1 - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](pyca/cryptography@46.0.3...48.0.1) Updates `idna` from 3.11 to 3.15 - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md) - [Commits](kjd/idna@v3.11...v3.15) Updates `urllib3` from 2.5.0 to 2.7.0 - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](urllib3/urllib3@2.5.0...2.7.0) --- updated-dependencies: - dependency-name: requests dependency-version: 2.33.0 dependency-type: direct:production dependency-group: uv - dependency-name: python-dotenv dependency-version: 1.2.2 dependency-type: direct:production dependency-group: uv - dependency-name: cryptography dependency-version: 48.0.1 dependency-type: direct:production dependency-group: uv - dependency-name: idna dependency-version: '3.15' dependency-type: indirect dependency-group: uv - dependency-name: urllib3 dependency-version: 2.7.0 dependency-type: indirect dependency-group: uv ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): pin transitive dependency resolutions in browseros-agent (#1997) Refresh a batch of pinned transitive resolutions to their current patch releases via overrides and update bun.lock. * Revert "fix(claw-app): scale Audit tab navigation (#1987)" (#1998) This reverts commit ee46ec4. * feat(claw): rewrite BrowserClaw harness skill (#1999) Frontmatter description now carries the standing instruction — default to BrowserClaw for browser work unprompted, over in-app browser surfaces — and the body becomes a compact operating manual: session etiquette, task-owned tabs, the snapshot -> act -> verify loop, read/grep and tool escalation, failure handling, and the untrusted-page-content rule. Validation test probes and line budget updated to match. * fix(agent): reconcile aliased skill targets by identity (#2001) * fix(agent): reconcile aliased skill targets by identity * fix(claw): use app resources in local launchers * test(claw): assert physical skill manifest paths * fix: address review findings for browserclaw-skill-target-identity * feat(claw): show total tokens consumed on Audit surfaces (#2000) * feat(claw): show total tokens consumed on Audit surfaces Materialize per-session token estimates (input+output, summed across dispatches) onto the tasks table via recompute_task, so the total is available live and not only from the post-teardown efficiency projection. Expose it through the canonical OpenAPI path as an optional SessionSummary tokenUsage field, present only when the session has dispatches and every one carries token-estimator v1 (legacy/unmeasured sessions omit it). Both Audit surfaces render it from the one shared field: a compact Tokens column on the session list (exact count on hover, em dash when unmeasured) and a Tokens entry on the session-detail summary card. * fix(claw): sort unmeasured token totals last in both directions The Tokens-column accessor mapped unmeasured sessions to 0, which only sank them on a descending sort — an ascending sort surfaced them ahead of every measured session. Return undefined and use tanstack's sortUndefined:'last' so unmeasured rows stay at the bottom either way. * fix(claw): hide tokens in audit list (#2002) * fix(claw): ground screenshot token baseline (#2003) * fix(claw): ground screenshot token baseline * test(claw): update cockpit efficiency expectation * test(claw): preserve signed savings coverage * feat(claw): move audit observation off the tool path (#2005) * refactor(claw): separate tool observers from effects * feat(claw): queue audit observations in bounded worker * fix(claw): flush coalesced audit previews on session close * test(claw): verify async audit shutdown lifecycle * fix: address review findings for 260723-1154_async_audit_observer * fix(claw): sum tool durations for human time saved (#2004) * fix(claw): sum tool durations for time saved * fix(claw): preserve retained efficiency history * fix(claw): guard efficiency backfill from session reuse * fix(claw): stop cockpit saved-stats labels from overlapping (#2006) Both the "used" and "screenshot-first" token labels were absolutely positioned inside the same overflow-hidden budget track. Around the mid-range their text occupied the same pixels and painted through each other (e.g. "used 63.2K" over "112.3K"). Lift both numbers into a legend row above the bar — a flex justify-between row that wraps to two lines before the labels could ever meet — and keep the track as a pure gauge: accent fill = what BrowserClaw used, striped remainder = what a screenshot-first agent would have spent. The bar carries no text now, so nothing can collide or clip at any used/total ratio or label length. Accessibility, reduced-motion (the pulsing dot keeps motion-reduce), theme tokens, and the stats/API contract are unchanged. Tests assert the structural invariant (no labels inside the bounded bar) across low, mid, and full ratios, and the fill width replaces the old marker-position assertions. * refactor(patches): retire hidden window implementation (#2007) * refactor(patches): retire hidden window implementation * fix(patches): preserve single-close window teardown * chore: sync internal-docs submodule (#2008) Co-authored-by: browseros-bot <bot@browseros.ai> * chore: sync internal-docs submodule (#2009) Co-authored-by: browseros-bot <bot@browseros.ai> * fix(claw): use session duration for human time saved (#2010) * fix(claw): use session duration for saved time * fix(claw): backfill saved time from task durations * perf(core): acquire snapshots concurrently (#2011) * perf(core): batch cursor backend-node resolution * refactor(core): separate snapshot frame acquisition * perf(core): acquire snapshot sibling frames concurrently * perf(core): trace concurrent snapshot capture * fix: address review findings for snapshot-concurrent-acquisition * fix: address review findings for snapshot-concurrent-acquisition * fix: address review findings for snapshot-concurrent-acquisition * fix: suppress intentional cursor asset expression * fix(core): clear frame sessions after reconnect * fix(core): address snapshot PR review notes * feat(claw-app): default replays to 2x (#2015) * chore: refresh TypeScript CDP and retire hidden surfaces (#2014) * chore(cdp): refresh TypeScript protocol definitions * refactor(server): use background pages for scheduled tasks * refactor(browser-core): retire hidden page and window behavior * refactor(browser-mcp): remove hidden tab and window controls * test: repair TypeScript verification baselines * test(claw-app): prepare WXT aliases before tests * fix(claw): exclude red from automatic tab-group colors (#2016) * fix(claw): exclude red from automatic tab groups * test(claw): update automatic tab color fixture * test(claw): cover snapshot concurrency contracts (#2017) * test(claw): add snapshot concurrency fixtures * test(claw): cover live cursor snapshot acquisition * test(claw): cover concurrent iframe snapshot contracts * test(claw): stress overlapping snapshot contracts * fix: address review findings for snapshot-concurrency-contract-tests * fix: address review findings for snapshot-concurrency-contract-tests * refactor: refresh Rust CDP and retire hidden windows (#2013) * chore(cdp): sync Rust protocol from Chromium * refactor(core): retire hidden window state * refactor(mcp): remove hidden window controls * refactor(server): drop hidden window runtime references * test(core): align getProperties fixtures with protocol * test(mcp): retire hidden conformance cases * chore(release): build v0.48.3 [skip ci] Automated nightly macOS version bump. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nikhil <nikhilsv92@gmail.com> Co-authored-by: browseros-bot <bot@browseros.ai> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * feat(claw): advertise full MCP tool catalog * test(claw): align prompt assertion with main * chore(claw): refresh generated dispatch model * chore(claw): sync generated Rust dispatch docs * chore(claw): sync generated OpenAPI client --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Dani Akash <DaniAkash@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: browseros-bot <bot@browseros.ai> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
Design decisions
Test plan
cd packages/browseros-agent && cargo fmt --all --checkcd packages/browseros-agent && cargo clippy --workspace --all-targets -- -D warningscd packages/browseros-agent && cargo test --workspaceonclickin same-process iframe and OOPIF